from torch.utils import data 
import numpy as np
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor, Lambda, Compose
from collections import  Counter
import random
import os
import logging
import torch
from math import sqrt
from scipy.stats import wasserstein_distance,energy_distance

'''参数：作用
%(levelno)s：打印日志级别的数值
%(levelname)s：打印日志级别的名称
%(pathname)s：打印当前执行程序的路径，其实就是sys.argv[0]
%(filename)s：打印当前执行程序名
%(funcName)s：打印日志的当前函数
%(lineno)d：打印日志的当前行号
%(asctime)s：打印日志的时间
%(thread)d：打印线程ID
%(threadName)s：打印线程名称
%(process)d：打印进程ID
%(message)s：打印日志信息

logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
'''

'''用到的numpy方法总结
np.where()条件查找一个数组的索引（某一个类别的数组的索引）
np.array_split()直接划分数组
np.append()合并两个数组
np.random.randint/shffle/rand/permutation
np.unique()统计，并返回每个类别的数量
np.load()加载npy的数据。可以尝试用这种方法划分数据集并进行多机实验。
np.save()存储成npy的数据
'''


logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# import matplotlib.pyplot as plt

# 1. 从内存中加载数据并转换为numpy格式的数据。不同的数据集，需要重写load函数。划分训练集和测试集。这里的测试集合训练集对应的是测试客户端和训练客户端。如果没哟测试客户端返回空即可。另外，对于一个客户端上数据的细分，应该在客户端上进行。
# 2. 这里非独立同分布的划分方式是通用，不同的只有数据加载方式。针对返回的self.train_data,self.train_targets,self.test_data,self.test_targets等进行数据划分。
# 2. 根据非独立同分布的设置，讲数据划分为多分，并返回多分数据的numpy数据数据。因为返回dataset反而不利于进一步的划分和处理。
# 主要完成数据的多个客户端划分。不划分训练数据还是测试数据。因为可能存在一下多种情况：
#   a. 分为测试节点和训练节点
#   b. 每个客户端既包含测试数据也包含训练数据。单个客户端还可能面临其他情况的划分。

def mkdirs(dirpath):
    try:
        os.makedirs(dirpath)
    except Exception as _:
        pass


class Generated(MNIST):
    """Dataset主要继承torch.data.dataset，用于加载各式各样的数据到内存当中。

    Args:
        MNIST (基类): 继承了数据集类
    """    
    def __init__(self, root="", dataidxs=None, train=True, transform=None, target_transform=None,
                 download=False):
        super(MNIST, self).__init__(root, transform=transform,
                                    target_transform=target_transform)
        self.train = train
        self.dataidxs = dataidxs

        if self.train:
            self.data = np.load("../data/generated/X_train.npy")
            self.targets = np.load("../data/generated/y_train.npy")
        else:
            self.data = np.load("../data/generated/X_test.npy")
            self.targets = np.load("../data/generated/y_test.npy")            

        if self.dataidxs is not None:
            self.data = self.data[self.dataidxs]
            self.targets = self.targets[self.dataidxs]        


    def __getitem__(self, index):
        data, target = self.data[index], self.targets[index]
        return data, target

    def __len__(self):
        return len(self.data)


class AddGaussianNoise(object):
    def __init__(self, mean=0., std=1., net_id=None, total=0):
        self.std = std
        self.mean = mean
        self.net_id = net_id
        self.num = int(sqrt(total))
        if self.num * self.num < total:
            self.num = self.num + 1

    def __call__(self, tensor):
        if self.net_id is None:
            return tensor + torch.randn(tensor.size()) * self.std + self.mean
        else:
            tmp = torch.randn(tensor.size())
            filt = torch.zeros(tensor.size())
            size = int(28 / self.num)
            row = int(self.net_id / size)
            col = self.net_id % size
            for i in range(size):
                for j in range(size):
                    filt[:,row*size+i,col*size+j] = 1
            tmp = tmp * filt
            return tensor + tmp * self.std + self.mean

    def __repr__(self):
        return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std)


class DataManager(object):
    """通过Dataset获取数据。并通过继承的方式，获得基类中的一系列方法。组装数据和数据划分算法。

    Args:
        object (基类): 获得object的基础方法
    """    
    def __init__(self,dir='../data'):
        # 客户端的数量
        self.dir = dir
        self.train_data,self.train_targets,self.test_data,self.test_targets = self.load_data()
        # 发现这里的随机划分对结果的影响，太过于明显，在好的划分与差的划分下，结果准确值能够相差40%左右。太离谱了。
        # 对服务器上的随机数进行规范。在找到合适的随机数的时候能够进行保存.
        seed = np.random.randint(100000,999999)
        # seed = 681335
        np.random.seed(seed)
        logger.info("DataManager seed:{}".format(seed))

        # 显示相关的统计信息
        logger.info("训练集特征维度:{},{}".format(self.train_data.shape,self.train_data.dtype))
        logger.info("训练集标签维度:{},{}".format(self.train_targets.shape,self.train_targets.dtype))
        logger.info("训练集特征维度:{},{}".format(self.test_data.shape,self.train_data.dtype))
        logger.info("训练集标签维度:{},{}".format(self.test_targets.shape,self.train_targets.dtype))

        

    # 1 
    def load_data(self):
        """加载数据、并对数据进行预处理。加载numpy数据到内存中。以下是自动生成的数据
        Returns:
            [(X_train,y_train,X_test,y_test)]: [numpy格式的训练数据和测试数据]
        """        
        X_train, y_train = [], []
        for loc in range(4):
            for i in range(1000):
                p1 = random.random()
                p2 = random.random()
                p3 = random.random()
                if loc > 1:
                    p2 = -p2
                if loc % 2 ==1:
                    p3 = -p3
                if i % 2 == 0:
                    X_train.append([p1, p2, p3])
                    y_train.append(0)
                else:
                    X_train.append([-p1, -p2, -p3])
                    y_train.append(1)
        X_test, y_test = [], []
        for i in range(1000):
            p1 = random.random() * 2 - 1
            p2 = random.random() * 2 - 1
            p3 = random.random() * 2 - 1
            X_test.append([p1, p2, p3])
            if p1>0:
                y_test.append(0)
            else:
                y_test.append(1)
        X_train = np.array(X_train, dtype=np.float32)
        X_test = np.array(X_test, dtype=np.float32)
        y_train = np.array(y_train, dtype=np.int32)
        y_test = np.array(y_test, dtype=np.int64)
        idxs = np.linspace(0,3999,4000,dtype=np.int64)
        # batch_idxs = np.array_split(idxs, n_parties)
        # net_dataidx_map = {i: batch_idxs[i] for i in range(n_parties)}
        mkdirs("../data/generated/")
        np.save("../data/generated/X_train.npy",X_train)
        np.save("../data/generated/X_test.npy",X_test)
        np.save("../data/generated/y_train.npy",y_train)
        np.save("../data/generated/y_test.npy",y_test)
        return (X_train,y_train,X_test,y_test)
    

    def allocate_data_avg(self,client_number=20):
        """根据客户端的总量互斥平均，分配成client_number份。假定原来的数据已经shuffle了。

        Args:
            client_number (int, optional): [客户端的数量]. Defaults to 20.

        Returns:
            [dataset_list]: [数据列表[(x,y),]]
        """        
        data_size=len(self.train_data)
        data_counts = int(data_size/client_number)
        # 随机生成索引
        idxs = np.random.permutation(data_size)
        batch_idxs = np.array_split(idxs, client_number)
        dataset_list = [(self.train_data[batch_idxs[i]],self.train_targets[batch_idxs[i]]) for i in range(client_number)]

        # for i in range(client_number):
        #     # print(i*data_counts,(i+1)*data_counts)
        #     dataset_list.append((self.train_data[i*data_counts:(i+1)*data_counts],self.train_targets[i*data_counts:(i+1)*data_counts]))

        logger.info("avg----client_size:{}----data_counts:{}".format(client_number,data_counts))
        self.show_distribution(dataset_list)
        return dataset_list


    def allocate_data_iid(self,client_number=20,data_counts=3000):
        """ 从数据集中随机选取固定数量的数据。
        基于随机选择random.choice和numpy数组的花式索引实现。
        Args:
            client_number (int, optional): [表示客户端的数量]. Defaults to 20.
            data_counts (int, optional): [表示每个客户端具有的数据规模]. Defaults to 300.

        Returns:
            [dataset_list]: [数据列表[(x,y),]]
        """        
        # 数据随机选择
        dataset_list = []
        for i in range(client_number):
            idx = np.random.choice(range(len(self.train_data)),data_counts,replace=False)
            dataset_list.append((self.train_data[idx],self.train_targets[idx]))
        
        logger.info("iid----client_size:{}----data_counts:{}".format(client_number,data_counts))
        self.show_distribution(dataset_list)
        return dataset_list


    def allocate_data_noniid(self,client_number=20,data_counts=3000,class_total=10,class_counts=2):
        """从数据集中选取随机类别的固定数量的数据。一种典型的非独立同分布划分方法
        基于随机选择random.choice和numpy数组的花式索引实现。

        Args:
            client_number (int, optional): [客户端数量]. Defaults to 20.
            data_counts (int, optional): [数据规模]. Defaults to 3000.
            class_total (int, optional): [类别的总数]. Defaults to 10.
            class_counts (int, optional): [表示类别的数量]. Defaults to 2.

        Returns:
            [type]: [description]
        """        
        # 数据随机选择
        dataset_list = []
        
        # 每个客户端随机选择标签
        label_list = [np.random.choice(range(class_total),class_counts,replace=False) for i in range(client_number)]

        # 每个类别的数据放到一块
        data_class = []
        target_class = []
        for i in range(class_total):
            data_class.append(self.train_data[self.train_targets==i])
            target_class.append(np.ones(len(self.train_data[self.train_targets==i]),dtype=np.int64)*i)
        # 没办法直接转换成二维数组，因为第二个维度不一致
        # 给每个客户端选择指定数量数据，限制在某一类别。从lable_list确定的3个类中，选择数据。
        for labels in label_list:
            class_cat = []
            target_cat = []
            for i in labels:
                class_cat.append(data_class[i])
                target_cat.append(target_class[i])

            
            class_cat=np.vstack(class_cat)
            target_cat=np.hstack(target_cat)
 
            size = len(class_cat)
            idx = np.random.choice(range(size),data_counts,replace=False)
            dataset_list.append((class_cat[idx],target_cat[idx]))
        
        logger.info("noniid----client_size:{}----data_counts:{}----class_counts:{}".format(client_number,data_counts,class_counts))
        self.show_distribution(dataset_list)
        return dataset_list

    def allocate_data_noniid_by_label_number(self,client_number=20,class_total=10,class_counts=2):
        """基于数量的客户端标签不平衡。每一个客户端具有的标签类型不一样。数目也不一样。
        可以尝试更加经典的数据集划分方法。完全互斥。每一个类别平均划分，所有的客户端选择。

        Args:
            client_number (int, optional): [客户端数量]. Defaults to 20.
            class_total (int, optional): [类别的总数]. Defaults to 10.
            class_counts (int, optional): [单个客户端类别的数量]. Defaults to 2.

        Returns:
            [type]: [description]
        """        
        dataset_list = []
        # 平均分布
        if class_counts == class_total:
            net_dataidx_map ={i:np.ndarray(0,dtype=np.int64) for i in range(client_number)}
            for i in range(class_counts):
                idx_k = np.where(self.train_targets==i)[0]
                np.random.shuffle(idx_k)
                split = np.array_split(idx_k,client_number)
                for j in range(client_number):
                    net_dataidx_map[j]=np.append(net_dataidx_map[j],split[j])
        else:
            # 必然包含一个当前类。其他类自动生成。且某个类别分割的数量不一定一致。所以可能导致最后数量不平衡。
            times=[0 for i in range(class_total)]
            contain=[]
            for i in range(client_number):
                current=[i%class_total]
                times[i%class_total]+=1
                j=1
                while (j<class_counts):
                    ind=random.randint(0,class_total-1)
                    if (ind not in current):
                        j=j+1
                        current.append(ind)
                        times[ind]+=1
                contain.append(current)
            net_dataidx_map ={i:np.ndarray(0,dtype=np.int64) for i in range(client_number)}
            for i in range(class_total):
                idx_k = np.where(self.train_targets==i)[0]
                np.random.shuffle(idx_k)
                split = np.array_split(idx_k,times[i])
                ids=0
                for j in range(client_number):
                    if i in contain[j]:
                        net_dataidx_map[j]=np.append(net_dataidx_map[j],split[ids])
                        ids+=1

            for name in net_dataidx_map:
                dataset_list.append((self.train_data[net_dataidx_map[name]],self.train_targets[net_dataidx_map[name]]))

        logger.info("noniid_by_label_number----client_number:{}----class_total:{}----class_count:{}".format(client_number,class_total,class_counts))
        self.show_distribution(dataset_list)
        return dataset_list

    def allocate_data_noniid_by_label_number2(self,client_number=20,class_total=10,class_counts=2):
        """基于数量的客户端标签不平衡。每一个客户端具有的标签类型不一样。数目也不一样。
        
        完全互斥的一种划分方法。保重数据块不重叠，但是某个客户端选择的类别可能重叠
        1. 将数据集按类别划分。将类别划分成指定数量的块。将所有的块构成一个数组。class_total*block_number == client_number * class_counts
        2. 创建块的随机索引。按照客户端的数量进行划分。则每个客户端有class_counts个块。
        3. 通过花式所以，得到某个客户端的多个块中的索引。然后通过花式索引得到数据。

        Args:
            client_number (int, optional): [客户端数量]. Defaults to 20.
            class_total (int, optional): [类别的总数]. Defaults to 10.
            class_counts (int, optional): [单个客户端类别的数量]. Defaults to 2.

        Returns:
            [type]: [description]
        """        

        dataset_list = []
        # 必须保证能够整除划分
        assert (client_number*class_counts)%class_total==0, "(client_number*class_counts)模class_total==0必须能够整除"
        # 考虑每一个类别应该被划分成多少块。对数据集进行划分。
        # 将所有的块串联起来，构成一个array
        index_split = np.ndarray([0])
        part_number_per_class = client_number * class_counts / class_total
        for i in range(class_total):
            # 返回满足条件的索引
            class_index = np.where(self.train_targets==i)[0]
            # 混淆索引
            np.random.shuffle(class_index)
            # 将索引划分成指定的份数
            class_split = np.array_split(class_index,part_number_per_class)
            if(i==0):
                index_split = class_split
            else:
                index_split = np.append(index_split,class_split)

        # 根据客户端数量进行类别-块的随机选择。生成块的随机索引
        block_index = np.random.permutation(len(index_split))

        # 每个客户端随机选择多个类别的块。这个无法保证类别互斥。
        select_block = np.array_split(block_index,client_number)

        # 建立索引
        for block in select_block:
            data_index = np.hstack(index_split[block])
            dataset_list.append((self.train_data[data_index],self.train_targets[data_index]))
   
        logger.info("noniid_by_label_number----client_number:{}----class_total:{}----class_count:{}".format(client_number,class_total,class_counts))
        self.show_distribution(dataset_list)
        return dataset_list


    def allocate_data_noniid_by_label_distribution(self,client_number=20,class_total=10,beta=0.4):
        """基于分布的标签不平衡。

        Args:
            client_number (int, optional): [客户端的数量]. Defaults to 20.
            class_total (int, optional): [类别的数量]. Defaults to 10.
            beta (float, optional): [分布的参数]. Defaults to 0.4.

        Returns:
            [dataset_list]: [数据集]
        """        
        min_size = 0
        min_require_size = 10

        N = self.train_targets.shape[0]
        np.random.seed(2020)
        dataset_list = []

        while min_size < min_require_size:
            idx_batch = [[] for _ in range(client_number)]
            for k in range(class_total):
                idx_k = np.where(self.train_targets == k)[0]
                np.random.shuffle(idx_k)
                proportions = np.random.dirichlet(np.repeat(beta, client_number))
                # logger.info("proportions1: ", proportions)
                # logger.info("sum pro1:", np.sum(proportions))
                ## Balance
                proportions = np.array([p * (len(idx_j) < N / client_number) for p, idx_j in zip(proportions, idx_batch)])
                # logger.info("proportions2: ", proportions)
                proportions = proportions / proportions.sum()
                # logger.info("proportions3: ", proportions)
                proportions = (np.cumsum(proportions) * len(idx_k)).astype(int)[:-1]
                # logger.info("proportions4: ", proportions)
                idx_batch = [idx_j + idx.tolist() for idx_j, idx in zip(idx_batch, np.split(idx_k, proportions))]
                min_size = min([len(idx_j) for idx_j in idx_batch])
                # if K == 2 and n_parties <= 10:
                #     if np.min(proportions) < 200:
                #         min_size = 0
                #         break


        for j in range(client_number):
            np.random.shuffle(idx_batch[j])
            dataset_list.append((self.train_data[idx_batch[j]],self.train_targets[idx_batch[j]]))
        
        logger.info("noniid_by_label_distribution----client_size:{}----class_total:{}----beta:{}".format(client_number,class_total,beta))
        self.show_distribution(dataset_list)
        return dataset_list

    def allocate_data_noniid_by_quantity(self,client_number=20,beta=0.4):
        """客户端数据规模不平衡

        Args:
            client_number (int, optional): [客户端数量]. Defaults to 20.
            class_total (int, optional): [类别数量]. Defaults to 10.
            beta (float, optional): [分布参数]. Defaults to 0.4.

        Returns:
            [dataset_list]: [数据集]
        """        
        dataset_list = []
        idxs = np.random.permutation(self.train_targets.shape[0])
        min_size = 0
        min_require_size=10
        while min_size < min_require_size:
            proportions = np.random.dirichlet(np.repeat(beta, client_number))
            proportions = proportions/proportions.sum()
            min_size = np.min(proportions*len(idxs))
        proportions = (np.cumsum(proportions)*len(idxs)).astype(int)[:-1]
        batch_idxs = np.split(idxs,proportions)
        dataset_list = [(self.train_data[batch_idxs[i]],self.train_targets[batch_idxs[i]]) for i in range(client_number)]

        logger.info("noniid_by_quantity----client_number:{}----beta:{}".format(client_number,beta))
        self.show_distribution(dataset_list)
        return dataset_list


    def allocate_data_noniid_by_feature_noise(self,client_number=20,bias_level=0.1):
        """每个客户端添加不同的数据集的偏差。

        Args:
            client_number (int, optional):客户端熟练. Defaults to 20.
            class_total (int, optional):类别数量. Defaults to 10.
            bias_level (float, optional): bias_level偏差的影响力. Defaults to 0.4.

        Returns:
            [type]: [description]
        """        
        data_size=len(self.train_data)
        data_counts = int(data_size/client_number)
        # 随机生成索引
        idxs = np.random.permutation(data_size)
        batch_idxs = np.array_split(idxs, client_number)
        dataset_list = [(self.train_data[batch_idxs[i]],self.train_targets[batch_idxs[i]]) for i in range(client_number)]

        

        dataset_list_with_bias = []
        bias_list = []
        for datapair in dataset_list:
            bias = self.add_bias(datapair,bias_level)
            bias_list.append(bias)
            dataset_list_with_bias.append((datapair[0].copy()+bias,datapair[1]))
        logger.info("noniid_by_feature_noise----client_number:{}----data_counts:{}----bias_level:{}".format(client_number,data_counts,bias_level))
        self.show_distribution(dataset_list)
        return dataset_list_with_bias


    def add_bias(self,datapair,bias_level=0.1):
        """计算一个数据集的高斯偏差。每一个客户端的数据集，计算不同的高斯偏差。
        处理数据集，添加本地的高斯噪声。计算本地的高斯噪声向量，添加到每一条数据集。或者只是单纯的计算noise向量.
        噪声的比例。相当于给本地的数据添加了一个偏差，而非高斯噪声，所欲的输入数据都一样。
        我认为，这里每个客户端的高斯向量不同，但是同一个客户端的所有的数据的高斯噪声是一致的。
        客户端与客户端之间的差异，不应该是信噪比，或者说噪声的方差。而是噪声的方差应该是一致的，信噪比，是相同的。
        客户端与客户端之间的差异，应该是偏差，或者说高斯向量。在相同的方差下，计算出来的高斯向量的不同。

        Args:
            datapair ([x,y]): 一个客户端的输入输出
            bias_level (float, optional): 偏差占比，相对于原始数据的方差，只提供0.1的影响力. Defaults to 0.1.

        Returns:
            [type]: [description]
        """        
        # 计算本地的noise矩阵。给单个客户端计算的噪声是相同的。
        x,y = datapair
        variance = np.std(self.train_data,axis=0)
        bias_var = variance * bias_level
        # 偏差用高斯分布干嘛呢？随机高斯分布应该是纵向的，
        # 在客户端之间的数据符合高斯分布，而不是在本地自己的内容是高斯分布
        bias = np.random.standard_normal(size = x[0].shape)
        # 限制其最大值不超过1
        bias = bias/bias.max()
        # 减掉均值乘以方差
        bias = bias*bias_var
        return bias
        
    def allocate_data_test(self):
        """测试数据集

        Returns:
            [dataset_list]: [当前只支持，单个测试客户端，拥有所有的测试数据]
        """        
        dataset_list = [(self.test_data,self.test_targets),]
        self.show_distribution(dataset_list)
        return  dataset_list

    
    # 使用KL散度计算当前数据非独立同分布的程度（或者其他方式，用来衡量划分后的数据的非独立同分布的程度）
    def calculate_degree_by_energy(self,dataset_list):
        """用来衡量数据分布差异的方法。
        energy_distance。据说是wasserstein距离的改进方法。
        后续需要看论文，把原理搞懂。
        Args:
            dataset_list ([(x,y)]): 客户单数据列表

        Returns:
            [type]:所有客户端两两之间的平均距离
        """        
        # 尝试一下scipy提供的距离计算方法
        distance_y = 0.0
        times_y = 0.0
        for i in range(len(dataset_list)):
            for j in range(len(dataset_list)):
                if(j>i):
                    times_y +=1
                    distance_y+=energy_distance(dataset_list[i][1],dataset_list[j][1])
        
        average_distance_y = distance_y/times_y

        # 计算前10个特征的距离
        distance_x = 0.0
        times_x = 0.0
        col = 20
        for i in range(len(dataset_list)):
            for j in range(len(dataset_list)):
                if(j>i):
                    for k in range(col):
                        times_x +=1
                        distance_x+=energy_distance(dataset_list[i][0][:][k],dataset_list[j][0][:][k])
        
        average_distance_x = distance_x/times_x
        return average_distance_x,average_distance_y 


    def calculate_degree_y_by_Wasserstein(self,dataset_list):
        """用来衡量数据分布的方法。方法主要包括以下几种
        Wasserstein距离。可以考虑后续实现其他的方法。
        虽然找到了衡量数据非独立同分布的集成方法，但是你需要直到计算原理，用来作报告。
        Args:
            dataset_list (): 划分好的数据集
        """        
        # 尝试一下scipy提供的距离计算方法
        distance_y = 0.0
        times_y = 0.0
        for i in range(len(dataset_list)):
            for j in range(len(dataset_list)):
                if(j>i):
                    times_y +=1
                    distance_y+=wasserstein_distance(dataset_list[i][1],dataset_list[j][1])
        
        average_distance_y = distance_y/times_y

        return average_distance_y
    
    
    def calculate_degree_x_by_Wasserstein(self,dataset_list):
        """用来衡量数据分布的方法。方法主要包括以下几种
        Wasserstein距离。可以考虑后续实现其他的方法。
        虽然找到了衡量数据非独立同分布的集成方法，但是你需要直到计算原理，用来作报告。
        Args:
            dataset_list (): 划分好的数据集
        """        
        # 计算前10个特征的距离
        distance_x = 0.0
        times_x = 0.0
        col = 100
        for i in range(len(dataset_list)):
            for j in range(len(dataset_list)):
                if(j>i):
                    for k in range(col):
                        times_x +=1
                        a = dataset_list[i][0].reshape(dataset_list[i][0].shape[0],-1)[:,k]
                        b = dataset_list[j][0].reshape(dataset_list[j][0].shape[0],-1)[:,k]
                        distance_x+=wasserstein_distance(a,b)
        
        average_distance_x = distance_x/times_x
        return average_distance_x

    # 用来显示dataset_list的分布的差异。主要是统计每个数据集中每个y的数量。
    def show_distribution(self,dataset_list):
        logger.info('数据统计结果:')
        for data in dataset_list:
            logger.info("数量:{}  分布:{}".format(len(data[1]),Counter(data[1])) )

    def record_net_data_stats(self,dataset_list):
        net_cls_counts = []
        for _, y in dataset_list:
            unq, unq_cnt = np.unique(y, return_counts=True)
            tmp = {unq[i]: unq_cnt[i] for i in range(len(unq))}
            net_cls_counts.append(tmp)
        logger.info('Data statistics: {}'.format(net_cls_counts))
        return net_cls_counts


# 使用单例模式加载一个数据集。
# 在import的时候，执行这段代码，将全局变量dataManager导入到内存当中。
# dataManager = DataManager()